using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Net;
using CMUSHrun.Objects;
using System.Text.RegularExpressions;

namespace CMUSHrun.Server
{
    /// <summary>
    /// The server instance
    /// 
    /// TODO This and the TCP code needs a heavy review to verify that this is the best
    /// way to handle this.  Threaded inputs may not be the wisest, with a single blocking
    /// lock.
    /// </summary>
    class MushServer
    {
        private const int PortNumber = 4000;
        private const int BacklogSize = 20;

        /// <summary>
        /// The list of connections
        /// </summary>
        private List<Server.TcpConnection> _connections = new List<TcpConnection>();

        /// <summary>
        /// The mush database
        /// </summary>
        private CMUSHrun.Database.MushDatabase _database = new CMUSHrun.Database.MushDatabase();

        /// <summary>
        /// The main application loop, the listener
        /// </summary>
        public void MainLoop()
        {
            Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            server.Bind(new IPEndPoint(IPAddress.Any, PortNumber));
            server.Listen(BacklogSize);

            while (true)
            {
                Socket conn = server.Accept();
                TcpConnection tcpUser = new TcpConnection(conn);
                tcpUser.Disconnected += new EventHandler<TcpStreamDisconnectEventArgs>(tcpUser_Disconnected);
                tcpUser.TcpStreamTextReceivedEvent += new EventHandler<TcpStreamTextReceivedEventArgs>(tcpUser_TcpStreamTextReceivedEvent);
                _connections.Add(tcpUser);

                //// For giggles, we will create a new object for now.
                MushPlayer mp = _database.NewPlayer();
                _database.GetByID(0).AddToInventory(mp);
                tcpUser.BindMushPlayer(mp);
            }
        }

        /// <summary>
        /// Receiving text stream from the user stream
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void tcpUser_TcpStreamTextReceivedEvent(object sender, TcpStreamTextReceivedEventArgs e)
        {
            // get the TCP user
            TcpConnection tcpUser = (TcpConnection)sender;

            // If the user is not logged in, they are very limited in what they can do:
            // WHO, IDLE, CONNECT, CON, CREATE, QUIT
            if (!tcpUser.IsLoggedIn)
            {
                UnAuthCommands(tcpUser, e.Text);
                return;
            }


            // There are certain text elements that are not considered commands, they are meta commands 
            // like LOGOUT, CREATE, CONNECT, IDLE, and WHO.  These commands are beyond the scope of the application code
            // and should be filtered out and handled here
            switch (e.Text)
            {
                case "LOGOUT":
                    // If the user is logged in, we dump them out and unbind them
                    tcpUser.BindMushPlayer(null);
                    tcpUser.WriteLine("Logged out.");
                    return;
                
                case "QUIT":
                    tcpUser.WriteLine("Thanks for visiting!");
                    tcpUser.Disconnect();
                    return;

                case "IDLE":
                    tcpUser.WriteLine("IDLE: This shouldn't show anything, but allow the connection to continue.");
                    return;

                case "WHO":
                    tcpUser.WriteLine("WHO: This would show a WHO list.");
                    return;

            }


            // Tell the user what they did
            tcpUser.WriteLine("You entered a non-os command: " + e.Text);

            //TODO determine how to know where an object is that is talking so we can send it to the right places
            foreach (MushObject mo in e.User.Container.GetInventory())
            {
                // We have the list of objects that this thing contains. output the text here
                mo.TextToObject(e.User, e.Text);
            }
        }

        /// <summary>
        /// Executes the commands that are allowed when the user is not authorized
        /// </summary>
        /// <param name="tcpUser"></param>
        /// <param name="line"></param>
        private void UnAuthCommands(TcpConnection tcpUser, String line)
        {
            // We can always disconnect
            if (line == "QUIT")
            {
                tcpUser.Disconnect();
                return;
            }

            // IDLE allows systems that time out from dying
            if (line == "IDLE")
            {
                tcpUser.WriteLine("IDLE: Don't show this text.");
                return;
            }

            // And let the user see who's connected
            if (line == "WHO")
            {
                tcpUser.WriteLine("WHO: To be determined.");
                return;
            }


            // Do the stuff when we AREN'T logged in
            Regex r;
            r = new Regex("^\\s*(CREATE|CONNECT|CON|CONN)\\s+(\\w+)\\s+(\\w+)\\s*$", RegexOptions.IgnoreCase);
            Match m = r.Match(line);
            if (m.Success)
            {
                // We were successful in getting a create | connect string
                tcpUser.WriteLine("Thank you " + m.Groups[2].Captures[0]);

                //// example from MS help
                //int matchCount = 0;

                //while (m.Success)
                //{
                //    Console.WriteLine("Match" + (++matchCount));
                //    for (int i = 1; i <= m.Groups.Count; i++)
                //    {
                //        Group g = m.Groups[i];
                //        Console.WriteLine("Group" + i + "='" + g + "'");
                //        CaptureCollection cc = g.Captures;
                //        for (int j = 0; j < cc.Count; j++)
                //        {
                //            Capture c = cc[j];
                //            System.Console.WriteLine("Capture" + j + "='" + c + "', Position=" + c.Index);
                //        }
                //    }
                //    m = m.NextMatch();
                //}


            }
            else
            {
                tcpUser.WriteLine("Please enter 'QUIT', 'WHO', 'CREATE <player> <password>' or 'CONNECT <player> <password>' without the quotes.");
            }
        }

        /// <summary>
        /// Called when the user disconnects
        /// </summary>
        /// <param name="sender">The Tcp Connection object</param>
        /// <param name="e">Event ARgs</param>
        void tcpUser_Disconnected(object sender, TcpStreamDisconnectEventArgs e)
        {
            _connections.Remove((TcpConnection)sender);
            foreach (MushObject mo in e.User.Container.GetInventory())
            {
                if (mo != e.User)
                {
                    // This isn't the "me" object
                    mo.TextToObject(e.User, "has disconnected.");
                }
            }

            //foreach (TcpConnection conn in _connections)
            //{
            //    conn.WriteLine("A user has disconnected.");
            //}
        }
    }
}
